/********************************************************************************
 * Copyright (c) {date} Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License, Version 2.0 which is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0 
 ********************************************************************************/
import com.intellij.ide.highlighter {
    JavaClassFileType
}
import com.intellij.openapi.util {
    Key,
    Ref
}
import com.intellij.openapi.vfs {
    VirtualFile
}

import java.lang {
    ObjectArray
}

import org.eclipse.ceylon.ide.intellij.model {
    Annotations
}
import org.jetbrains.org.objectweb.asm {
    ClassReader {
        skipCode,
        skipDebug,
        skipFrames
    },
    ClassVisitor,
    Opcodes,
    AnnotationVisitor,
    Attribute
}

"Helper to determine if a .class file was generated by the Ceylon compiler."
shared object classFileDecompilerUtil {

    shared Key<CeylonBinaryData> ceylonBinaryDataKey
            = Key<CeylonBinaryData>("CEYLON_BINARY_DATA_KEY");

    shared Boolean isCeylonCompiledFile(VirtualFile file)
            => if (exists ext = file.extension,
                    ext == JavaClassFileType.instance.defaultExtension)
            then file.\iexists() && file.length > 0 &&
                getCeylonBinaryData(file).ceylonCompiledFile
            else false;

    String ann(String className) => "L" + className.replace(".", "/") + ";";

    shared CeylonBinaryData getCeylonBinaryData(VirtualFile file) {
        if (exists userData = file.getUserData(ceylonBinaryDataKey),
            userData.timestamp == file.timeStamp) {

            return userData;
        }

        value isCeylon = Ref(false);
        value isIgnore = Ref(false);
        value isInner = Ref(false);

        try {
            value reader = ClassReader(file.contentsToByteArray());
            value className = reader.className;

            reader.accept(
                object extends ClassVisitor(Opcodes.asm5) {
                    shared actual AnnotationVisitor? visitAnnotation(String desc, Boolean visible) {
                        if (desc ==ann(Annotations.ceylon.className)) {
                            isCeylon.set(true);
                        }
                        if (desc ==ann(Annotations.ignore.className)) {
                            isIgnore.set(true);
                        }
                        return super.visitAnnotation(desc, visible);
                    }

                    visitOuterClass(String owner, String name, String desc) => isInner.set(true);

                    shared actual void visitInnerClass(String name, String outerString, String inner, Integer access) {
                        if (className == name) {
                            isInner.set(true);
                        }
                    }
                },
                ObjectArray<Attribute>(0, null),
                skipCode.or(skipDebug).or(skipFrames)
            );
        } catch (e) {
            // just say this is not a Ceylon class
        }

        value data = CeylonBinaryData {
            timestamp = file.timeStamp;
            ceylonBinary = isCeylon.get();
            ceylonIgnore = isIgnore.get();
            inner = isInner.get();
        };
        file.putUserData(ceylonBinaryDataKey, data);
        return data;
    }

    shared Boolean hasValidCeylonBinaryData(VirtualFile? file)
            => file?.getUserData(ceylonBinaryDataKey)?.ceylonBinary
            else false;
}